Completed
Pull Request — master (#134)
by Sander
49s
created

background.js ➔ ... ➔ API.tabs.then   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
/* global API */
2
3
var background = (function () {
4
    var storage = new API.Storage();
5
    var _self = this;
6
    var _window = {};
7
8
9
    API.runtime.onConnect.addListener(function (port) {
10
11
        port.onMessage.addListener(function (msg) {
12
            if (msg === 'credential_amount') {
13
                port.postMessage('credential_amount:' + local_credentials.length);
14
            }
15
16
        });
17
18
    });
19
20
    var master_password = null;
21
22
    function getMasterPasswordSet() {
23
        return (master_password !== null);
24
    }
25
26
    _self.getMasterPasswordSet = getMasterPasswordSet;
27
28
    function setMasterPassword(opts) {
29
        master_password = opts.password;
30
        if (opts.hasOwnProperty('savePassword') && opts.savePassword === true) {
31
            // Save the password in plain text on user request.
32
            // No secure local storage is available :/
33
            storage.set('master_password', opts.password);
34
        } else {
35
            storage.set('master_password', null);
36
        }
37
38
        if (opts.password) {
39
            getSettings();
40
        } else {
41
            displayLogoutIcons();
42
        }
43
44
    }
45
46
    _self.setMasterPassword = setMasterPassword;
47
48
49
    var testMasterPasswordAgainst;
50
51
    function isMasterPasswordValid(password) {
52
        try {
53
            PAPI.decryptString(testMasterPasswordAgainst, password);
54
            return true;
55
        } catch (e) {
56
            return false;
57
        }
58
    }
59
60
    _self.isMasterPasswordValid = isMasterPasswordValid;
61
62
63
    var local_credentials = [];
64
    var local_vault = [];
65
    var encryptedFieldSettings = ['accounts'];
66
    _self.settings = {};
67
    _self.ticker = null;
68
    _self.running = false;
69
    function getSettings() {
70
71
        storage.get('settings').then(function (_settings) {
72
            if ((!_settings || Object.keys(_settings).length === 0 || !_settings.hasOwnProperty('accounts')) && !master_password) {
73
                return;
74
            }
75
76
            if (!master_password && _settings.hasOwnProperty('accounts') && _settings.accounts.length > 0) {
77
                _self.settings.isInstalled = 1;
78
                testMasterPasswordAgainst = _settings.accounts;
79
                return;
80
            }
81
82
            for (var i = 0; i < encryptedFieldSettings.length; i++) {
83
                var field = encryptedFieldSettings[i];
84
                _settings[field] = JSON.parse(PAPI.decryptString(_settings[field], master_password));
85
            }
86
87
            _self.settings = _settings;
88
89
            if (!_self.settings.hasOwnProperty('ignored_sites')) {
90
                _self.settings.ignored_sites = [];
91
            }
92
93
            if (!_self.settings.hasOwnProperty('no_results_found_tab')) {
94
                _self.settings.no_results_found_tab = 'list';
95
            }
96
97
            getCredentials();
98
99
            if (_self.running) {
100
                clearInterval(_self.ticker);
101
            }
102
            _self.running = true;
103
            _self.ticker = setInterval(function () {
104
105
            }, _self.settings.refreshTime * 1000);
106
107
        });
108
    }
109
110
    _self.getSettings = getSettings;
111
112
    function getRuntimeSettings() {
113
        return _self.settings;
114
    }
115
116
    _self.getRuntimeSettings = getRuntimeSettings;
117
118
    function getSetting(name) {
119
        return _self.settings[name];
120
    }
121
122
    _self.getSetting = getSetting;
123
124
    function saveSettings(settings, cb) {
0 ignored issues
show
Unused Code introduced by
The parameter cb is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
125
        for (var i = 0; i < encryptedFieldSettings.length; i++) {
126
            var field = encryptedFieldSettings[i];
127
            settings[field] = PAPI.encryptString(JSON.stringify(settings[field]), master_password);
128
        }
129
130
        if (!settings.hasOwnProperty('ignored_sites')) {
131
            settings.ignored_sites = [];
132
        }
133
134
        if (!_self.settings.hasOwnProperty('password_picker_first_tab')) {
135
            _self.settings.disable_browser_autofill = 'list';
136
        }
137
138
        //window.settings contains the run-time settings
139
        _self.settings = settings;
140
141
142
        storage.set('settings', settings).then(function () {
143
            getSettings();
144
        });
145
146
    }
147
148
    _self.saveSettings = saveSettings;
149
150
151
    function getCredentials() {
152
        if (!master_password) {
153
            return;
154
        }
155
        //console.log('Loading vault with the following settings: ', settings);
156
        var tmpList = [];
157
158
        for (var i = 0; i < _self.settings.accounts.length; i++) {
159
            var account = _self.settings.accounts[i];
160
            /* jshint ignore:start */
161
            (function (inner_account) {
162
                PAPI.getVault(inner_account, function (vault) {
163
                    if (vault.hasOwnProperty('error')) {
164
                        return;
165
                    }
166
                    var _credentials = vault.credentials;
167
                    for (var i = 0; i < _credentials.length; i++) {
168
                        var key = inner_account.vault_password;
169
                        var credential = _credentials[i];
170
                        if (credential.hidden === 1) {
171
                            continue;
172
                        }
173
                        var usedKey = key;
174
                        //Shared credentials are not implemented yet
175
                        if (credential.hasOwnProperty('shared_key') && credential.shared_key) {
176
                            usedKey = PAPI.decryptString(credential.shared_key, key);
177
178
                        }
179
                        credential = PAPI.decryptCredential(credential, usedKey);
180
                        credential.account = inner_account;
181
                        if (credential.delete_time === 0) {
182
                            tmpList.push(credential);
183
                        }
184
185
                    }
186
                    delete vault.credentials;
187
                    local_vault = vault;
188
                    local_credentials = tmpList;
189
190
                    getSharedCredentials(inner_account);
191
192
193
                });
194
            }(account));
195
            /* jshint ignore:end */
196
        }
197
    }
198
199
    _self.getCredentials = getCredentials;
200
201
    function getSharedCredentials(account) {
202
        PAPI.getCredendialsSharedWithUs(account, account.vault.guid, function (credentials) {
203
            for (var i = 0; i < credentials.length; i++) {
204
                var _shared_credential = credentials[i];
205
                var _shared_credential_data;
206
                var sharedKey = PAPI.decryptString(_shared_credential.shared_key, account.vault_password);
207
                try {
208
                    _shared_credential_data = PAPI.decryptSharedCredential(_shared_credential.credential_data, sharedKey);
209
                } catch (e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
210
211
                }
212
                if (_shared_credential_data) {
213
                    delete _shared_credential.credential_data;
214
                    _shared_credential_data.acl = _shared_credential;
215
                    _shared_credential_data.acl.permissions = new SharingACL(_shared_credential_data.acl.permissions);
0 ignored issues
show
Bug introduced by
The variable SharingACL seems to be never declared. If this is a global, consider adding a /** global: SharingACL */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
216
                    _shared_credential_data.tags_raw = _shared_credential_data.tags;
217
                    _shared_credential_data.account = account;
218
                    local_credentials.push(_shared_credential_data);
219
                }
220
            }
221
            updateTabsIcon();
222
        });
223
    }
224
225
    function getCredentialsByUrl(_url, sender) {
0 ignored issues
show
Unused Code introduced by
The parameter sender is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
226
        if (!master_password) {
227
            return [];
228
        }
229
        if (!_url || _url === '') {
230
            return [];
231
        }
232
        var url = processURL(_url, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
233
        var found_list = [];
234
        for (var i = 0; i < local_credentials.length; i++) {
235
            var credential_url = local_credentials[i].url;
236
            if (!/^(ht)tps?:\/\//i.test(credential_url) && credential_url !== '' && _url) {
237
                try {
238
                    var protocol = _url.split('://').shift();
239
                    credential_url = protocol + "://" + credential_url;
240
                } catch (e){
241
                    //ignore
242
                }
243
            }
244
            credential_url = processURL(credential_url, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
245
            if (credential_url) {
246
                if (credential_url.indexOf(url) !== -1) {
247
                    found_list.push(local_credentials[i]);
248
                }
249
            }
250
251
        }
252
        return found_list;
253
    }
254
255
    _self.getCredentialsByUrl = getCredentialsByUrl;
256
257
258
    function saveCredential(credential) {
259
        //@TODO save shared password
260
        if (!credential.credential_id) {
261
            PAPI.createCredential(credential.account, credential, credential.account.vault_password, function (createdCredential) {
262
                local_credentials.push(createdCredential);
263
            });
264
        } else {
265
            var credential_index;
266
            for (var i = 0; i < local_credentials.length; i++) {
267
                if (local_credentials[i].guid === credential.guid) {
268
                    credential_index = i;
269
                    break;
270
                }
271
            }
272
273
            if (credential.hasOwnProperty('acl')) {
274
                var permissons = new SharingACL(credential.acl.permissions.permission);
0 ignored issues
show
Bug introduced by
The variable SharingACL seems to be never declared. If this is a global, consider adding a /** global: SharingACL */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
275
                if (!permissons.hasPermission(0x02)) {
276
                    return;
277
                }
278
            }
279
280
            PAPI.updateCredential(credential.account, credential, credential.account.vault_password, function (updatedCredential) {
281
                if (credential_index) {
282
                    local_credentials[credential_index] = updatedCredential;
283
                }
284
            });
285
        }
286
    }
287
288
    _self.saveCredential = saveCredential;
289
290
    function getCredentialByGuid(guid) {
291
        for (var i = 0; i < local_credentials.length; i++) {
292
            var credential = local_credentials[i];
293
            if (credential.guid === guid) {
294
                return credential;
295
            }
296
        }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
297
    }
298
299
    _self.getCredentialByGuid = getCredentialByGuid;
300
301
    function getCredentialForHTTPAuth(req) {
302
        return getCredentialsByUrl(req.url)[0];
303
    }
304
305
    _window.getCredentialForHTTPAuth = getCredentialForHTTPAuth;
306
307
    var mined_data = [];
308
309
    function minedForm(data, sender) {
310
        var url = sender.url;
311
        var existingLogins = getCredentialsByUrl(sender.url);
312
        var title = API.i18n.getMessage('detected_new_login') + ':';
313
        var minedMatchingID = null;
314
        for (var j = 0; j < existingLogins.length; j++) {
315
            var login = existingLogins[j];
316
            if (login.username === data.username) {
317
                if (login.password !== data.password) {
318
                    minedMatchingID = login.guid;
319
                    title = API.i18n.getMessage('detected_changed_login') + ':';
320
                }
321
                else {
322
                    //console.log('No changes detected');
323
                    delete mined_data[sender.tab.id];
324
                    return;
325
                }
326
            }
327
        }
328
        mined_data[sender.tab.id] = {
329
            title: title,
330
            url: url,
331
            username: data.username,
332
            password: data.password,
333
            label: sender.title,
334
            guid: minedMatchingID
335
        };
336
337
        //console.log('Done mining, ', mined_data, sender.tab.id);
338
    }
339
340
    _self.minedForm = minedForm;
341
342
    function getMinedData(args, sender) {
343
        //console.log('Fecthing  mined data for tab id', sender.tab.id)
344
        var senderUrl = sender.tab.url;
345
        var site = processURL(senderUrl, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
346
        if (!_self.settings) {
347
            return null;
348
        }
349
        if (!_self.settings.hasOwnProperty('ignored_sites')) {
350
            return mined_data[sender.tab.id];
351
        }
352
        var matches = _self.settings.ignored_sites.filter(function (item) {
353
            return typeof item === 'string' && site.indexOf(item) > -1;
354
        });
355
356
        if (matches.length !== 0) {
357
            return null;
358
        }
359
        return mined_data[sender.tab.id];
360
    }
361
362
    _self.getMinedData = getMinedData;
363
364
    function clearMined(args, sender) {
365
        delete mined_data[sender.tab.id];
366
    }
367
368
    _self.clearMined = clearMined;
369
370
    function saveMinedCallback(args) {
371
        createIconForTab(args.sender.tab);
372
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
373
            API.tabs.sendMessage(args.sender.tab.id, {method: "minedLoginSaved", args: args}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
374
            });
375
        });
376
    }
377
378
    function ignoreSite(_url) {
379
        if (!_self.settings.hasOwnProperty('ignored_sites')) {
380
            _self.settings.ignored_sites = [];
381
        }
382
        var site = processURL(_url, _self.settings.ignoreProtocol, _self.settings.ignoreSubdomain, _self.settings.ignorePath, _self.settings.ignorePort);
383
        if (_self.settings.ignored_sites.indexOf(site) === -1) {
384
            _self.settings.ignored_sites.push(site);
385
            saveSettings(_self.settings);
386
        }
387
    }
388
389
    _self.ignoreSite = ignoreSite;
390
391
    function passToParent(args, sender) {
392
        API.tabs.sendMessage(sender.tab.id, {method: args.injectMethod, args: args.args}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
393
        });
394
    }
395
396
    _self.passToParent = passToParent;
397
398
    function getActiveTab(opt) {
399
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
400
            var tab = tabs[0];
401
            API.tabs.sendMessage(tab.id, {method: opt.returnFn, args: tab}).then(function (response) {
0 ignored issues
show
Unused Code introduced by
The parameter response is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
402
            });
403
        });
404
    }
405
406
    _self.getActiveTab = getActiveTab;
407
408
    function updateCredentialUrlDoorhanger(login) {
409
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
410
            var tab = tabs[0];
411
            var data = login;
412
            data.url = tab.url;
413
            data.title = API.i18n.getMessage('detected_changed_url') + ':';
414
            API.tabs.sendMessage(tab.id, {
415
                method: 'showUrlUpdateDoorhanger',
416
                args: {data: data}
417
            });
418
        });
419
    }
420
421
    _self.updateCredentialUrlDoorhanger = updateCredentialUrlDoorhanger;
422
423
    function updateCredentialUrl(data, sender) {
424
        mined_data[sender.tab.id] = data;
425
        saveMined({}, sender);
426
427
    }
428
429
    _self.updateCredentialUrl = updateCredentialUrl;
430
431
    function saveMined(args, sender) {
432
        var data = mined_data[sender.tab.id];
433
        var credential = {},
434
            credential_index;
435
436
        if (data.guid === null) {
437
            credential = PAPI.newCredential();
438
        } else {
439
            for (var i = 0; i < local_credentials.length; i++) {
440
                if (local_credentials[i].guid === data.guid) {
441
                    credential = local_credentials[i];
442
                    credential_index = i;
443
                    break;
444
                }
445
            }
446
        }
447
        if (!credential.hasOwnProperty('account')) {
448
            credential.account = args.account;
449
        }
450
        credential.username = data.username;
451
        credential.password = data.password;
452
        credential.url = sender.tab.url;
453
        if (credential.guid !== null) {
454
            PAPI.updateCredential(credential.account, credential, credential.account.vault_password, function (updatedCredential) {
455
                updatedCredential.account = credential.account;
456
                if (credential_index) {
457
                    local_credentials[credential_index] = updatedCredential;
458
                }
459
                saveMinedCallback({credential: credential, updated: true, sender: sender});
460
                delete mined_data[sender.tab.id];
461
            });
462
        } else {
463
            credential.label = sender.tab.title;
464
            credential.vault_id =  credential.account.vault.vault_id;
465
            PAPI.createCredential(credential.account, credential, credential.account.vault_password, function (createdCredential) {
466
                createdCredential.account = credential.account;
467
                saveMinedCallback({credential: credential, updated: false, sender: sender});
468
                local_credentials.push(createdCredential);
469
                delete mined_data[sender.tab.id];
470
            });
471
        }
472
    }
473
474
    _self.saveMined = saveMined;
475
476
    function searchCredential(searchText) {
477
        searchText = searchText.toLowerCase();
478
        var searchFields = ['label', 'username', 'email', 'url', 'description'];
479
        var results = [];
480
        for (var i = 0; i < local_credentials.length; i++) {
481
            var credential = local_credentials[i];
482
            for (var f = 0; f < searchFields.length; f++) {
483
                var field = searchFields[f];
484
                if (!credential[field]) {
485
                    continue;
486
                }
487
488
                var field_value = credential[field].toLowerCase();
489
                if (field_value.indexOf(searchText) !== -1) {
490
                    results.push(credential);
491
                    break;
492
                }
493
            }
494
        }
495
        return results;
496
    }
497
498
    _self.searchCredential = searchCredential;
499
500
501
    function injectCreateCredential(args, sender) {
502
        var account = getRuntimeSettings().accounts[args.vaultIndex];
503
        var credential = PAPI.newCredential();
504
        credential.label = args.label;
505
        credential.username = args.username;
506
        credential.password = args.password;
507
        credential.vault_id = local_vault.vault_id;
508
        credential.url = sender.tab.url;
509
        PAPI.createCredential(account, credential, account.vault_password, function (createdCredential) {
510
            saveMinedCallback({credential: credential, updated: false, sender: sender, selfAdded: true});
511
            local_credentials.push(createdCredential);
512
513
        });
514
    }
515
516
    self.injectCreateCredential = injectCreateCredential;
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
517
518
    function isVaultKeySet() {
519
        return (_self.settings.vault_password !== null);
520
    }
521
522
    _self.isVaultKeySet = isVaultKeySet;
523
524
    function isAutoFillEnabled() {
525
        if (!_self.settings.hasOwnProperty('disableAutoFill')) {
526
            return true;
527
        }
528
        return (_self.settings.disableAutoFill === false);
529
    }
530
531
    _self.isAutoFillEnabled = isAutoFillEnabled;
532
533
    var doorhangerData = null;
534
535
    function setDoorhangerData(data) {
536
        doorhangerData = data;
537
    }
538
539
    _self.setDoorhangerData = setDoorhangerData;
540
541
    function getDoorhangerData() {
542
        return doorhangerData;
543
    }
544
545
    _self.getDoorhangerData = getDoorhangerData;
546
547
    function closeSetupTab() {
548
        API.tabs.query({url: 'chrome-extension://' + API.runtime.id + '/html/browser_action/browser_action.html'}).then(function (tabs) {
549
            if (tabs) {
550
                API.tabs.remove(tabs[0].id);
551
            }
552
        });
553
    }
554
555
    _self.closeSetupTab = closeSetupTab;
556
557
    API.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
558
559
        if (!msg || !msg.hasOwnProperty('method')) {
560
            return;
561
        }
562
        var result = false;
563
        if (_self[msg.method]) {
564
            result = _self[msg.method](msg.args, sender);
565
        } else {
566
            console.warn('[NOT FOUND] Method call', msg.method, 'args: ', msg.args);
567
        }
568
569
        sendResponse(result);
570
    });
571
572
    var defaultColor = '#0082c9';
573
574
    function createIconForTab(tab) {
575
        if (!master_password) {
576
            return;
577
        }
578
        var tabUrl = tab.url;
579
        var logins = getCredentialsByUrl(tabUrl);
580
        if (tab.active) {
581
            window.contextMenu.setContextItems(logins);
582
        }
583
        var credentialAmount = logins.length;
584
        API.browserAction.setBadgeText({
585
            text: credentialAmount.toString(),
586
            tabId: tab.id
587
        });
588
        API.browserAction.setBadgeBackgroundColor({
589
            color: defaultColor,
590
            tabId: tab.id
591
        });
592
593
        var plural = (credentialAmount === 1) ? API.i18n.getMessage('credential') : API.i18n.getMessage('credentials');
594
        API.browserAction.setTitle({
595
            title: API.i18n.getMessage('browser_action_title_login', [credentialAmount.toString(), plural.toString()]),
596
            tabId: tab.id
597
        });
598
    }
599
600
    function displayLogoutIcons() {
601
        if (_self.settings) {
602
            API.tabs.query({}).then(function (tabs) {
603
                for (var t = 0; t < tabs.length; t++) {
604
                    var tab = tabs[t];
605
                    API.browserAction.setBadgeText({
606
                        text: '🔑',
607
                        tabId: tab.id
608
                    });
609
                    API.browserAction.setBadgeBackgroundColor({
610
                        color: '#ff0000',
611
                        tabId: tab.id
612
                    });
613
                    API.browserAction.setTitle({
614
                        title: API.i18n.getMessage('browser_action_title_locked'),
615
                        tabId: tab.id
616
                    });
617
                }
618
            });
619
        }
620
    }
621
622
    function updateTabsIcon() {
623
        API.tabs.query({}).then(function (tabs) {
624
            for (var t = 0; t < tabs.length; t++) {
625
                var tab = tabs[t];
626
                createIconForTab(tab);
627
            }
628
        });
629
    }
630
631
632
    API.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
633
        if (master_password) {
634
            createIconForTab(tab);
635
        } else {
636
            displayLogoutIcons();
637
        }
638
    });
639
640
    API.tabs.onActivated.addListener(function () {
641
        API.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
642
            if (master_password) {
643
                createIconForTab(tabs[0]);
644
            } else {
645
                displayLogoutIcons();
646
            }
647
        });
648
    });
649
650
    displayLogoutIcons();
651
652
653
    storage.get('master_password').then(function (password) {
654
        if (password) {
655
            master_password = password;
656
            API.api.browserAction.setBadgeBackgroundColor({
657
                color: defaultColor
658
            });
659
        }
660
        getSettings();
661
    }).error(function (error) {
662
        if (error === "Data not found") {
663
            getSettings();
664
        }
665
    });
666
    return _window;
667
}());
668
669